package cn.qingfeng.demo.transaction.springboot.declarative.transactionNest.service.impl;

import cn.qingfeng.demo.transaction.springboot.common.entity.User;
import cn.qingfeng.demo.transaction.springboot.common.mapper.UserMapper;
import cn.qingfeng.demo.transaction.springboot.declarative.transactionNest.service.TransactionNestInnerService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.UUID;

@Service
//@Transactional//可以用在类上或方法上，都用时以方法上的为准
public class TransactionNestInnerServiceImpl implements TransactionNestInnerService {//这里不要忘记实现接口，否则controller中注入不了。
    //注入用户Mapper
    @Resource(name = "userMapperTransaction")
    private UserMapper userMapper;


    /**
     * 支持（加入）当前事务
     * 如果当前没有事务，就新建一个事务，如果已经存在一个事务，加入到这个事务中。这是最常见的选择。
     */
    @Transactional//(propagation = Propagation.REQUIRED)
    @Override
    public void testRequired() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

//        System.out.println(1 / 0);//这里出异常的话，内层外层一起回滚

    }

    @Transactional//(propagation = Propagation.REQUIRED)
    @Override
    public void testRequired2() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

        System.out.println(1 / 0);//仅这里出异常的话，内层外层一起回滚。外层虽然trycatch了，但是无效。Propagation.REQUIRED传播机制下只要内层回滚了，就会触发外层回滚。

    }

    /**
     * 支持当前事务
     * 支持当前事务，如果当前没有事务，就以非事务方式执行。
     * 这种方式与Properties.REQUIRED类似，区别仅在于当前无事务时。
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public void testSupports() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

        System.out.println(1 / 0);//这里出异常的话，内层外层一起回滚
    }

    /**
     * 支持（使用）当前事务
     * 使用当前的事务，如果当前没有事务，就抛出异常。
     * 这种方式与Properties.REQUIRED类似，区别仅在于当前无事务时。
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public void testMandatory() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

//        System.out.println(1/0);//这里出异常的话，内层外层一起回滚
    }

    /**
     * 嵌入当前事务【错误的写法】
     */
    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void testNested() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

        System.out.println(1 / 0);//这里出异常的话，内层外层一起回滚。外层回滚的原因是异常上抛被外层事务监听到了。
    }

    /**
     * 【错误的写法】
     */
    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void testNested2() {
        try {
            User user = new User("内层", UUID.randomUUID().toString());
            userMapper.add(user);
            System.out.println("inner向数据库中插入了User----" + user);

            System.out.println(1 / 0);//仅这里出异常的话，内层不会回滚，因为异常被捕获处理掉了，并没有被内层事务感知到。外层事务也感知不到异常。内层外层都正常提交。
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

    /**
     * 【正确的写法】
     * 如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，则执行与PROPAGATION_REQUIRED类似的操作。
     * 嵌套在事务内执行的意思是内层事务开始执行时设置一个savepoint，内层事务有异常的话就回滚到这个保存点然后外层事务正常执行。如果内外事务都没有异常，那么外层事务执行完后内外事务一起提交。
     * 但是内层事务有异常时会将异常上抛，导致外层事务也回滚。这就失去了nested传播机制的意义，变得跟required机制相同了。因此在调用nested机制的方法时需要trycatch处理一下，阻止异常上抛。
     */
    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void testNested3() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

        System.out.println(1 / 0);//仅这里异常的话，内层回滚，外层正常提交。因为外层进行了trycatch，阻止了异常的继续上抛。
    }

    /**
     * 【错误的写法】【特殊情况下或可一用】
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void testRequiredNew() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

        System.out.println(1 / 0);//仅这里出异常的话，内层外层一起回滚。外层回滚的原因是异常上抛被外层事务监听到了。
    }

    /**
     * 【错误的写法】
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void testRequiredNew2() {
        try {
            User user = new User("内层", UUID.randomUUID().toString());
            userMapper.add(user);
            System.out.println("inner向数据库中插入了User----" + user);

//            System.out.println(1 / 0);//仅这里出异常的话，内层不会回滚，因为异常被捕获处理掉了，并没有被内层事务感知到。外层事务也感知不到异常。内层外层都正常提交。
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新建事务【正确的写法】
     * 新建事务，如果当前存在事务，把当前事务挂起。（外层回滚的话不会引起内层回滚，内层回滚的话会连同外层一起回滚）
     * 挂起当前事务并新建事务的意思是内层事务与外层事务互不相干，任何一个回滚都不会导致另一个回滚。
     * 内层事务开始执行时设置一个savepoint，内层事务有异常的话就回滚到这个保存点然后外层事务正常执行。如果内外事务都没有异常，那么内层事务执行完之后立刻提交，然后继续执行外层事务。外层事务执行完后单独提交外层事务。
     * 但是内层事务有异常时会将异常上抛，导致外层事务也回滚。这就失去了requirednew传播机制的意义，实用价值不大。因此在调用requirednew机制的方法时需要trycatch处理一下，阻止异常上抛。
     * requirednew与nested区别在于前者比后者多了一个内层事务的提交。
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void testRequiredNew3() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

//        System.out.println(1 / 0);//仅这里出异常的话，内层回滚，外层正常提交
    }

    /**
     * 【不推荐】【特殊业务场景下或可一用】
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void testNotSupported() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

//        System.out.println(1/0);//仅这里出异常的话，内层的add方法成功插入数据库，外层事务回滚。外部事务回滚的原因是内层方法异常上抛。
    }

    /**
     * 非事务方式执行【推荐】
     * 以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void testNotSupported2() {
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);

//        System.out.println(1/0);//仅这里出异常的话，内层的add方法成功插入数据库，外层事务正常提交。
    }

    /**
     * 非事务方式执行
     * 以非事务方式执行，如果当前存在事务，则抛出异常。
     */
    @Transactional(propagation = Propagation.NEVER)
    @Override
    public void testNever() {
        //刚调用本方法时就会抛出异常，下面的代码都不会执行
        User user = new User("内层", UUID.randomUUID().toString());
        userMapper.add(user);
        System.out.println("inner向数据库中插入了User----" + user);
    }
}
